package gozero

import (
	"database/sql"
	"fmt"
	"reflect"
	"sync"
	"time"
)

var once sync.Once
var ctxDB *sql.DB

type Ctx struct {
	db        *sql.DB
	rows      *sql.Rows
	resultSet ResultSet
	err       error
}

type Cfg struct {
	MaxIdleConns    int
	MaxOpenConns    int
	ConnMaxLifetime time.Duration
}

var defaultCfg = Cfg{
	MaxIdleConns:    5,
	MaxOpenConns:    10,
	ConnMaxLifetime: 1 * time.Hour,
}

func GetCtx(db *sql.DB) Ctx {
	return Ctx{db: db}
}

func Open(driverName, dataSourceName string, cfg Cfg) Ctx {
	ctx := Ctx{}
	once.Do(func() {
		db, err := sql.Open(driverName, dataSourceName)
		if err != nil {
			panic(err)
		}
		if err = db.Ping(); err != nil {
			panic(err)
		}
		if reflect.DeepEqual(cfg, Cfg{}) {
			cfg = defaultCfg
		}
		db.SetMaxIdleConns(cfg.MaxIdleConns)
		db.SetMaxOpenConns(cfg.MaxOpenConns)
		db.SetConnMaxIdleTime(cfg.ConnMaxLifetime)
		ctxDB = db
	})
	ctx.db = ctxDB
	return ctx
}

func (ctx Ctx) Install(model interface{}) error {
	if ctx.err != nil {
		return ctx.err
	}
	ctx.fetchResultSet()
	m, err := newModel(model)
	if err != nil {
		return err
	}
	m.install(ctx.resultSet)
	return nil
}

func (ctx Ctx) Sql(query string, args ...interface{}) Ctx {
	if ctx.err != nil {
		return ctx
	}
	rows, err := ctx.db.Query(query, args...)
	if err != nil {
		ctx.err = fmt.Errorf("query %s error: %v", query, err)
	}
	ctx.rows = rows
	return ctx
}

// 获取结果集
func (ctx *Ctx) fetchResultSet() {
	colTyps, _ := ctx.rows.ColumnTypes()

	keys := make([]string, len(colTyps))
	vals := make([]interface{}, len(colTyps))
	ctx.resultSet = makeResultSet(0)
	for index, colTyp := range colTyps {
		keys[index] = colTyp.Name()
		switch colTyp.DatabaseTypeName() {
		case "BIT":
			vals[index] = &sql.NullByte{}
		case "BOOL":
			vals[index] = &sql.NullBool{}
		case "TINYINT":
			vals[index] = &sql.NullInt16{}
		case "INT":
			vals[index] = &sql.NullInt32{}
		case "BIGINT":
			vals[index] = &sql.NullInt64{}
		case "MEDIUMINT", "DOUBLE":
			vals[index] = &sql.NullFloat64{}
		case "CHAR", "VARCHAR", "TEXT", "LONGTEXT":
			vals[index] = &sql.NullString{}
		case "DATE", "DATETIME", "TIMESTAMP":
			vals[index] = &sql.NullTime{}
		default:
			vals[index] = &sql.NullString{}
		}
	}
	for ctx.rows.Next() {
		err := ctx.rows.Scan(vals...)
		if err != nil {
			ctx.err = fmt.Errorf("结果集获取错误 %v", err)
		}
		item := makeResultItem(0)
		for index := range vals {
			switch typ := vals[index].(type) {
			case *sql.NullByte:
				if typ.Valid {
					item[keys[index]] = typ.Byte
				}
			case *sql.NullBool:
				if typ.Valid {
					item[keys[index]] = typ.Bool
				}
			case *sql.NullInt16:
				if typ.Valid {
					item[keys[index]] = typ.Int16
				}
			case *sql.NullInt32:
				if typ.Valid {
					item[keys[index]] = typ.Int32
				}
			case *sql.NullInt64:
				if typ.Valid {
					item[keys[index]] = typ.Int64
				}
			case *sql.NullFloat64:
				if typ.Valid {
					item[keys[index]] = typ.Float64
				}
			case *sql.NullTime:
				if typ.Valid {
					item[keys[index]] = typ.Time
				}
			case *sql.NullString:
				if typ.Valid {
					item[keys[index]] = typ.String
				}
			}
		}
		ctx.resultSet = append(ctx.resultSet, item)
	}
}
